package cn.serein.charging.cost.service.impl;


import cn.serein.charging.common.pojo.param.ChargingProcessParam;
import cn.serein.charging.common.pojo.vo.ChargingProcessVO;
import cn.serein.charging.cost.dao.repository.ChargingDataRepository;
import cn.serein.charging.cost.dao.repository.ChargingRuleRepository;
import cn.serein.charging.cost.dao.repository.CostRuleCashRepository;
import cn.serein.charging.cost.pojo.param.ChargingData;
import cn.serein.charging.cost.pojo.po.ChargingDataPO;
import cn.serein.charging.cost.pojo.po.CostRulePO;
import cn.serein.charging.cost.service.CostService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;


@Slf4j
@Service
public class CostServiceImpl implements CostService {

    // 使用map去区分数据同步的次数，
    private Map<String, ChargingData> chargingProcessData = new HashMap<>();

    @Override
    public ChargingProcessVO calculateCost(ChargingProcessParam chargingProcessParam) {

        // 获取对应的充电数据
        ChargingData chargingData =  getChargingData(chargingProcessParam);
        log.debug("获取到当前订单的充电数据:{}",chargingData);

        // 使用elseasticsearch去代替本地map
        ChargingDataPO chargingDataPO = getChargingDataByES(chargingProcessParam);
        log.debug("获取到当前订单的充电数据:{}",chargingDataPO);

        // 获取当前时间
        int hour = getHour();
        // 根据时间  站id  和枪id  获取对应的计费规则  跨域封装成一个方法
        CostRulePO  costRulePO =  getMatchCostRuleByGunAndTime(chargingProcessParam.getStationId(),
                chargingProcessParam.getGunId(), hour);

        log.info("获取当前时间 小时:{},获取计价规则:{}",hour, costRulePO);

        // 判断计费规则是否存在
        if(costRulePO != null){
            // 计算费用
            String name = costRulePO.getName();
            // 拿到单价
            BigDecimal powerFee = costRulePO.getPowerFee();
            
            // 判断是否是第一次计算费用
            // 充血模型
            if(chargingDataPO.isFirstCharging()){
                firstCost2ChargingData(chargingProcessParam,chargingDataPO,name,powerFee,hour);
            }else{
                leastCost2ChargingData(chargingProcessParam,chargingDataPO,name,powerFee,hour);
            }
            
        }else{
            log.debug("没有匹配到对应的计费规则,告警");
        }

        ChargingProcessVO chargingProcessVO = new ChargingProcessVO();
        chargingProcessVO.setGunId(chargingProcessParam.getGunId());

        // 设置需要返回的参数
        return buildChargingProcessVO(chargingDataPO);


    }

    private ChargingProcessVO buildChargingProcessVO(ChargingDataPO chargingDataPO) {

        // 调用者需要的数据
        ChargingProcessVO chargingProcessVO = new ChargingProcessVO();
        chargingProcessVO.setOrderNo(chargingDataPO.getOrderNo());

        // 计算单价的时候  使用总价格/总充电度数
        BeanUtils.copyProperties(chargingDataPO,chargingProcessVO);

        return chargingProcessVO;
    }

    /**
     * 使用es查询数据
     * @param chargingProcessParam
     * @return
     */

    @Autowired
    private ChargingDataRepository chargingDataRepository;
    private ChargingDataPO getChargingDataByES(ChargingProcessParam chargingProcessParam) {

        // 从es里面查询数据

        // 拿到订单号
        String orderNo = chargingProcessParam.getOrderNo();

        Optional<ChargingDataPO> optional = chargingDataRepository.findById(orderNo);
        if(optional.isPresent()){
            // 从es里面获取数据
            ChargingDataPO chargingDataPO = optional.get();
            // 需要count + 1
            chargingDataPO.setCount(chargingDataPO.getCount()+1);
            chargingDataRepository.save(chargingDataPO);
            return chargingDataPO;
        }else{
            // es初始化
            ChargingDataPO chargingDataPO =  initESChargingData(chargingProcessParam);
            chargingDataRepository.save(chargingDataPO);

            return chargingDataPO;
        }
    }

    private ChargingDataPO initESChargingData(ChargingProcessParam chargingProcessParam) {
        ChargingDataPO chargingDataPO = new ChargingDataPO();
        BeanUtils.copyProperties(chargingProcessParam,chargingDataPO);
        chargingDataPO.setCount(1);
        chargingDataPO.setStartTime(System.currentTimeMillis());
        // 设置id
        chargingDataPO.setId(chargingProcessParam.getOrderNo());
        return chargingDataPO;
    }

    private ChargingData getChargingData(ChargingProcessParam chargingProcessParam) {
        // 拿到订单号
        String orderNo = chargingProcessParam.getOrderNo();
        // cong map里面拿到  判断是否存在  不存在  初始化
        ChargingData chargingData = chargingProcessData.get(orderNo);
        if(chargingData == null){
            //  chargingData初始化
            chargingData = initChargingData(chargingProcessParam);
            // 存入到map当中
            chargingProcessData.put(orderNo,chargingData);
        }else{
            chargingData.setCount(chargingData.getCount()+1);
            chargingProcessData.put(orderNo,chargingData);
        }
        return chargingData;
    }

    private ChargingData initChargingData(ChargingProcessParam chargingProcessParam) {
        ChargingData chargingData = new ChargingData();
//        chargingData.setOrderNo(chargingProcessParam.getOrderNo());
//        chargingData.setGunId(chargingProcessParam.getGunId());
//        chargingData.setUserId(chargingProcessParam.getUserId());
        BeanUtils.copyProperties(chargingProcessParam,chargingData);  // 代码优化
        chargingData.setCount(1);
        chargingData.setStartTime(System.currentTimeMillis());

        return chargingData;
    }

    private void leastCost2ChargingData(ChargingProcessParam chargingProcessParam, ChargingDataPO chargingData, String name, BigDecimal powerFee, int hour) {

       /* // 从chargingData里面拿到上一次存的总充电度数
        Float lastChargingCapacity = chargingData.getChargingCapacity();
        // cong chargingProcessParam里面拿到当前充电总度数
        Float totalChargingCapacity = chargingProcessParam.getChargingCapacity();

        // 本次充电度数
        Float currentChargingCapacity = totalChargingCapacity - lastChargingCapacity;*/


        //获取本次充电真实的度数
        Float realChargingCapacity = getRealChargingCapacity(chargingProcessParam, chargingData);
        // 计算本次的充电费用  （本次总度数 - 上次总度数） * 电费单价
        BigDecimal currentCost = getCost(realChargingCapacity, powerFee);

        // 计算总费用
        // BigDecimal totalCost = chargingData.getTotalCost().add(currentCost);
        // 更新chargingData里面的总费用  和  充电的总度数
        // 使用充血模型计算价格
        chargingData.setTotalCapacity(chargingProcessParam.getChargingCapacity());
        chargingData.setTotalCost(currentCost);
        //
        String startTime = String.valueOf(chargingData.getStartTime());
        log.debug("开始时间:{}",startTime);
        // 获取当前时间
        LocalDateTime now = LocalDateTime.now();
        log.debug("当前时间:{}",now);

//        String alreadyTime = String.valueOf(ChronoUnit.HOURS.between(now, LocalDateTime.parse(startTime)));
        String alreadyTime = "null";
        log.debug("已经充电:{}",alreadyTime);
        // 计算充电时间
        chargingData.setTotalTime(alreadyTime);




        chargingData.setChargingCapacity(realChargingCapacity);

        log.debug("当时时间(小时):{}," +
                "计价规则名称:{}," +
                "充电度数:{}," +
                "总充电度数:{}" +
                "电费单价:{}," +
                "本次花费:{},总费用:{}",hour,name,realChargingCapacity,
                chargingProcessParam.getChargingCapacity(),powerFee,currentCost,chargingData.getTotalCost()
        );
        // 需要更新到es里面去
        chargingDataRepository.save(chargingData);
    }

    private Float getRealChargingCapacity(ChargingProcessParam chargingProcessParam, ChargingDataPO chargingData) {

        // 从chargingData里面拿到上一次存的总充电度数
        Float lastChargingCapacity = chargingData.getChargingCapacity();
        // cong chargingProcessParam里面拿到当前充电总度数
        Float totalChargingCapacity = chargingProcessParam.getChargingCapacity();

        // 本次充电度数
        return totalChargingCapacity - lastChargingCapacity;

    }

    // 第一次充电
    private void firstCost2ChargingData(ChargingProcessParam chargingProcessParam,
                                        ChargingDataPO chargingData,String name,
                                        BigDecimal powerFee, int hour) {
//        log.debug("第一次计算费用");
        // float和BigDecimal进行转换
        // 拿到第一次的充电度数
        Float chargingCapacity = chargingProcessParam.getChargingCapacity();
        log.debug("第一次充电度数:{}",chargingCapacity);

        BigDecimal cost = getCost(chargingCapacity, powerFee);

        chargingData.setTotalCapacity(chargingProcessParam.getChargingCapacity());

        // 第一次计算好总费用之后，需要将数据存到chargingData里面
        chargingData.setTotalCost(cost);
        // 也需要将总度数传入到chargingData里面
        chargingData.setChargingCapacity(chargingCapacity);

        // 记得保存数据
        chargingDataRepository.save(chargingData);

        log.debug("当时时间(小时):{}," +
                "计价规则名称:{}," +
                "充电度数:{}," +
                "电费单价:{},本次花费:{}",hour,name,chargingCapacity,powerFee,cost
        );
    }

    // 计算费用
    private BigDecimal getCost(Float chargingCapacity, BigDecimal powerFee) {
        // float和BigDecimal进行转换
        // 先将float转换为String
        String stringChargingCapacity = Float.toString(chargingCapacity);
        BigDecimal bigDecimalChargingCapacity = new BigDecimal(stringChargingCapacity);
        return powerFee.multiply(bigDecimalChargingCapacity);
    }

//    private boolean isFirst(Integer count) {
//
//        return count = 1;
//    }


    private CostRulePO getMatchCostRuleByGunAndTime(Integer stationId, Integer gunId, int hour) {

        List<CostRulePO>  costRules =  getCostRules(stationId);

        // 循环遍历
        for (CostRulePO costRule : costRules) {
            // 判断时间是否在范围内
            if (costRule.getStartTime() <= hour && costRule.getEndTime() > hour) {
                // 判断枪类型是否匹配
                return costRule;
            }
        }

        return null;
    }
    // 装配repository
    @Autowired
    private ChargingRuleRepository chargingRuleRepository;

    @Autowired
    private CostRuleCashRepository costRuleCashRepository;


    private List<CostRulePO> getCostRules(Integer stationId) {

        /**
         * 考虑到并发问题，需要将规则放入到redis中 TODO
          */
        // 先去redis中获取
        List<CostRulePO> costCashRulePOS = costRuleCashRepository.getCashCostRules(stationId);
        if(!CollectionUtils.isEmpty(costCashRulePOS)){
            log.debug("从redis中获取到数据:{}",costCashRulePOS);
            // 如果有数据，直接返回
            return costCashRulePOS;

        }else{
            // 先从数据库里面获取数据
            List<CostRulePO> costDbRulePOS = chargingRuleRepository.getCostRules(stationId);
            log.debug("从数据库中获取到数据:{}",costDbRulePOS);
            if (!CollectionUtils.isEmpty(costDbRulePOS)){
                // 在redis里面添加
                costRuleCashRepository.setCashCostRules(stationId,costDbRulePOS);
                return costDbRulePOS;
            }
        }

//        // 通过站id  获取对应的计费规则
//        List<CostRulePO> costRulePOS = chargingRuleRepository.getCostRules(stationId);
//        log.debug("通过场站id:{},获取计价规则列表:{}",stationId,costRulePOS);

        return Collections.emptyList();
    }

    private int getHour() {
        // 获取当前的时间
        LocalDateTime now = LocalDateTime.now();

        // 获取当前小时   getHour()  和 getMinute()  分钟
        return now.getHour();
    }
}
